/*-------------------------------------------------------------------------
 *
 * rawpage.c
 *	  提取原始页面为 bytea 并进行检查的函数
 *
 * 访问方法特定的检查函数在单独的文件中。
 *
 * Copyright (c) 2007-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  contrib/pageinspect/rawpage.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/htup_details.h"
#include "access/relation.h"
#include "catalog/namespace.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "pageinspect.h"
#include "storage/bufmgr.h"
#include "storage/checksum.h"
#include "utils/builtins.h"
#include "utils/pg_lsn.h"
#include "utils/rel.h"
#include "utils/varlena.h"

PG_MODULE_MAGIC;

static bytea *fc_get_raw_page_internal(text *fc_relname, ForkNumber fc_forknum,
									BlockNumber fc_blkno);


/*
 * get_raw_page
 *
 * 返回共享缓冲区中页面的副本，格式为 bytea
 */
PG_FUNCTION_INFO_V1(get_raw_page_1_9);

Datum get_raw_page_1_9(PG_FUNCTION_ARGS)
{
	text	   *fc_relname = PG_GETARG_TEXT_PP(0);
	int64		fc_blkno = PG_GETARG_INT64(1);
	bytea	   *fc_raw_page;

	if (fc_blkno < 0 || fc_blkno > MaxBlockNumber)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("invalid block number")));

	fc_raw_page = fc_get_raw_page_internal(fc_relname, MAIN_FORKNUM, fc_blkno);

	PG_RETURN_BYTEA_P(fc_raw_page);
}

/*
 * 旧扩展版本的入口点
 */
PG_FUNCTION_INFO_V1(get_raw_page);

Datum get_raw_page(PG_FUNCTION_ARGS)
{
	text	   *fc_relname = PG_GETARG_TEXT_PP(0);
	uint32		fc_blkno = PG_GETARG_UINT32(1);
	bytea	   *fc_raw_page;

	/*
	 * 我们通常不会检查 C 函数的参数数量，但在这里为了安全
	 * 需要检查，因为早期 8.4 beta 版本错误地重新定义了
	 * get_raw_page() 使其接受三个参数。
	 */
	if (PG_NARGS() != 2)
		ereport(ERROR,
				(errmsg("wrong number of arguments to get_raw_page()"),
				 errhint("Run the updated pageinspect.sql script.")));

	fc_raw_page = fc_get_raw_page_internal(fc_relname, MAIN_FORKNUM, fc_blkno);

	PG_RETURN_BYTEA_P(fc_raw_page);
}

/*
 * get_raw_page_fork
 *
 * 适用于任何分叉的相同操作
 */
PG_FUNCTION_INFO_V1(get_raw_page_fork_1_9);

Datum get_raw_page_fork_1_9(PG_FUNCTION_ARGS)
{
	text	   *fc_relname = PG_GETARG_TEXT_PP(0);
	text	   *fc_forkname = PG_GETARG_TEXT_PP(1);
	int64		fc_blkno = PG_GETARG_INT64(2);
	bytea	   *fc_raw_page;
	ForkNumber	fc_forknum;

	fc_forknum = forkname_to_number(text_to_cstring(fc_forkname));

	if (fc_blkno < 0 || fc_blkno > MaxBlockNumber)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("invalid block number")));

	fc_raw_page = fc_get_raw_page_internal(fc_relname, fc_forknum, fc_blkno);

	PG_RETURN_BYTEA_P(fc_raw_page);
}

/*
 * 旧扩展版本的入口点
 */
PG_FUNCTION_INFO_V1(get_raw_page_fork);

Datum get_raw_page_fork(PG_FUNCTION_ARGS)
{
	text	   *fc_relname = PG_GETARG_TEXT_PP(0);
	text	   *fc_forkname = PG_GETARG_TEXT_PP(1);
	uint32		fc_blkno = PG_GETARG_UINT32(2);
	bytea	   *fc_raw_page;
	ForkNumber	fc_forknum;

	fc_forknum = forkname_to_number(text_to_cstring(fc_forkname));

	fc_raw_page = fc_get_raw_page_internal(fc_relname, fc_forknum, fc_blkno);

	PG_RETURN_BYTEA_P(fc_raw_page);
}

/*
 * 办事马
 */
static bytea * fc_get_raw_page_internal(text *fc_relname, ForkNumber fc_forknum, BlockNumber fc_blkno)
{
	bytea	   *fc_raw_page;
	RangeVar   *fc_relrv;
	Relation	fc_rel;
	char	   *fc_raw_page_data;
	Buffer		fc_buf;

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to use raw page functions")));

	fc_relrv = makeRangeVarFromNameList(textToQualifiedNameList(fc_relname));
	fc_rel = relation_openrv(fc_relrv, AccessShareLock);

	if (!RELKIND_HAS_STORAGE(fc_rel->rd_rel->relkind))
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot get raw page from relation \"%s\"",
						RelationGetRelationName(fc_rel)),
				 errdetail_relkind_not_supported(fc_rel->rd_rel->relkind)));

	/*
	 * 拒绝尝试读取非本地临时关系；我们可能会获取错误的数据，因为
	 * 我们无法查看拥有会话的本地缓冲区。
	 */
	if (RELATION_IS_OTHER_TEMP(fc_rel))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot access temporary tables of other sessions")));

	if (fc_blkno >= RelationGetNumberOfBlocksInFork(fc_rel, fc_forknum))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("block number %u is out of range for relation \"%s\"",
						fc_blkno, RelationGetRelationName(fc_rel))));

	/* 初始化要复制到的缓冲区 */
	fc_raw_page = (bytea *) palloc(BLCKSZ + VARHDRSZ);
	SET_VARSIZE(fc_raw_page, BLCKSZ + VARHDRSZ);
	fc_raw_page_data = VARDATA(fc_raw_page);

	/* 逐字复制页面 */

	fc_buf = ReadBufferExtended(fc_rel, fc_forknum, fc_blkno, RBM_NORMAL, NULL);
	LockBuffer(fc_buf, BUFFER_LOCK_SHARE);

	memcpy(fc_raw_page_data, BufferGetPage(fc_buf), BLCKSZ);

	LockBuffer(fc_buf, BUFFER_LOCK_UNLOCK);
	ReleaseBuffer(fc_buf);

	relation_close(fc_rel, AccessShareLock);

	return fc_raw_page;
}


/*
 * get_page_from_raw
 *
 * 从 get_raw_page() 的结果中获取一个 palloc'd、maxalign'ed 页面图像
 *
 * 在 MAXALIGN = 8 的机器上，bytea 的有效负载不是最大对齐的，
 * 因为它将从 palloc'd 值的第 4 个字节开始。在对齐要求严格的
 * 机器上，这将导致对页面内 8 字节宽值的访问失败。
 * 如果只访问 4 字节或更小的字段，则不必担心，
 * 但在检查包含 8 字段的结构时，请使用此函数以确保安全。
 */
Page get_page_from_raw(bytea *fc_raw_page)
{
	Page		fc_page;
	int			fc_raw_page_size;

	fc_raw_page_size = VARSIZE_ANY_EXHDR(fc_raw_page);

	if (fc_raw_page_size != BLCKSZ)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("invalid page size"),
				 errdetail("Expected %d bytes, got %d.",
						   BLCKSZ, fc_raw_page_size)));

	fc_page = palloc(fc_raw_page_size);

	memcpy(fc_page, VARDATA_ANY(fc_raw_page), fc_raw_page_size);

	return fc_page;
}


/*
 * page_header
 *
 * 允许检查原始页面的页面头字段
 */

PG_FUNCTION_INFO_V1(page_header);

Datum page_header(PG_FUNCTION_ARGS)
{
	bytea	   *fc_raw_page = PG_GETARG_BYTEA_P(0);

	TupleDesc	fc_tupdesc;

	Datum		fc_result;
	HeapTuple	fc_tuple;
	Datum		fc_values[9];
	bool		fc_nulls[9];

	PageHeader	fc_page;
	XLogRecPtr	fc_lsn;

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to use raw page functions")));

	fc_page = (PageHeader) get_page_from_raw(fc_raw_page);

	/* 为我们的结果类型构建一个元组描述符 */
	if (get_call_result_type(fcinfo, NULL, &fc_tupdesc) != TYPEFUNC_COMPOSITE)
		elog(ERROR, "return type must be a row type");

	/* 从页面头中提取信息 */

	fc_lsn = PageGetLSN(fc_page);

	/* pageinspect >= 1.2 使用 pg_lsn 代替 text 作为 LSN 字段。 */
	if (TupleDescAttr(fc_tupdesc, 0)->atttypid == TEXTOID)
	{
		char		fc_lsnchar[64];

		snprintf(fc_lsnchar, sizeof(fc_lsnchar), "%X/%X", LSN_FORMAT_ARGS(fc_lsn));
		fc_values[0] = CStringGetTextDatum(fc_lsnchar);
	}
	else
		fc_values[0] = LSNGetDatum(fc_lsn);
	fc_values[1] = UInt16GetDatum(fc_page->pd_checksum);
	fc_values[2] = UInt16GetDatum(fc_page->pd_flags);

	/* pageinspect >= 1.10 对这些字段使用 int4 而不是 int2 */
	switch (TupleDescAttr(fc_tupdesc, 3)->atttypid)
	{
		case INT2OID:
			Assert(TupleDescAttr(fc_tupdesc, 4)->atttypid == INT2OID &&
				   TupleDescAttr(fc_tupdesc, 5)->atttypid == INT2OID &&
				   TupleDescAttr(fc_tupdesc, 6)->atttypid == INT2OID);
			fc_values[3] = UInt16GetDatum(fc_page->pd_lower);
			fc_values[4] = UInt16GetDatum(fc_page->pd_upper);
			fc_values[5] = UInt16GetDatum(fc_page->pd_special);
			fc_values[6] = UInt16GetDatum(PageGetPageSize(fc_page));
			break;
		case INT4OID:
			Assert(TupleDescAttr(fc_tupdesc, 4)->atttypid == INT4OID &&
				   TupleDescAttr(fc_tupdesc, 5)->atttypid == INT4OID &&
				   TupleDescAttr(fc_tupdesc, 6)->atttypid == INT4OID);
			fc_values[3] = Int32GetDatum(fc_page->pd_lower);
			fc_values[4] = Int32GetDatum(fc_page->pd_upper);
			fc_values[5] = Int32GetDatum(fc_page->pd_special);
			fc_values[6] = Int32GetDatum(PageGetPageSize(fc_page));
			break;
		default:
			elog(ERROR, "incorrect output types");
			break;
	}

	fc_values[7] = UInt16GetDatum(PageGetPageLayoutVersion(fc_page));
	fc_values[8] = TransactionIdGetDatum(fc_page->pd_prune_xid);

	/* 构建并返回元组。 */

	memset(fc_nulls, 0, sizeof(fc_nulls));

	fc_tuple = heap_form_tuple(fc_tupdesc, fc_values, fc_nulls);
	fc_result = HeapTupleGetDatum(fc_tuple);

	PG_RETURN_DATUM(fc_result);
}

/*
 * page_checksum
 *
 * 计算原始页面的校验和
 */

PG_FUNCTION_INFO_V1(page_checksum_1_9);
PG_FUNCTION_INFO_V1(page_checksum);

static Datum fc_page_checksum_internal(PG_FUNCTION_ARGS, enum pageinspect_version fc_ext_version)
{
	bytea	   *fc_raw_page = PG_GETARG_BYTEA_P(0);
	int64		fc_blkno = (fc_ext_version == PAGEINSPECT_V1_8 ? PG_GETARG_UINT32(1) : PG_GETARG_INT64(1));
	Page		fc_page;

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to use raw page functions")));

	if (fc_blkno < 0 || fc_blkno > MaxBlockNumber)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("invalid block number")));

	fc_page = get_page_from_raw(fc_raw_page);

	if (PageIsNew(fc_page))
		PG_RETURN_NULL();

	PG_RETURN_INT16(pg_checksum_page((char *) fc_page, fc_blkno));
}

Datum page_checksum_1_9(PG_FUNCTION_ARGS)
{
	return fc_page_checksum_internal(fcinfo, PAGEINSPECT_V1_9);
}

/*
 * 旧扩展版本的入口点
 */
Datum page_checksum(PG_FUNCTION_ARGS)
{
	return fc_page_checksum_internal(fcinfo, PAGEINSPECT_V1_8);
}
